import { BackSide, DoubleSide, FrontSide, LinearFilter, LinearSRGBColorSpace, Matrix4, Mesh, Object3DEventMap, OrthographicCamera, SRGBColorSpace, TextureLoader, WebGLRenderTarget, WebGLRenderer } from "three";
import { App,  Point } from "../../Engine";
import Helper from "../../utils/Helper";
import Const from "../../application/constants/Const";
import Events from "../../application/constants/Events";
import { BaseTileMap } from "../../Docs";
import { CustomMeshBasicMaterial } from "../../renderExtension/Material/CustomMaterial";
import MaterialShaderManager from "../../renderExtension/Material/MaterialShaderManager";
import ShaderConst from "../../renderExtension/Material/ShaderConst";
import CustomPlaneGeometry from "../../renderExtension/geometriy/CustomPlaneGeometry";

/**
 * 压平分析
 * 该方案的思路如下：
 * 1. 在压平区域的正上方设置一个正交相机，确保正交相机的可视范围能够包含整个压平区域。然后以正交相机为视角渲染，把颜色信息写入到一个webglTarget中，并且把这一步渲染的view-project矩阵保存
 * 2. 使用正常相机渲染场景，在需要压平mesh材质的fragment shader中检测当前顶点是否在压平区域内，如果在压平区域内，用discard。另外在压平区域内渲染一个多边形，在多边形的fragment shader中使用当前的model矩阵乘以上一步的view-project矩阵，用得到的mvp矩阵乘以顶点，然后做透视除法，这样把顶点转换到了上一步的归一化坐标空间。然后把归一化坐标空间加上1，除以2,就转换到了纹理坐标，用纹理坐标从上一步的的纹理中采样
 * 其实这个方案和shadowMap的方案有些相似，只是第一步用的是正交相机，并且写入的是颜色信息，并且第二步把要压平区域内的pixel用discard扔掉，然后画多边形。
*/
export default class ClapAnalysis {
    private app: App;
    private renderer: WebGLRenderer;
    private preRenderTarget: WebGLRenderTarget;
    private previousVP: Matrix4;
    private data;
    private polygonMesh;
    private positionArray;
    private afterLoadTileListener;
    // private preLoadTileListener;
    // private analysis;
    private clapData: {isOpenClap: boolean, clapPointArray: [], clapPointCount: number, renderSide: number};

    private isOpenAnalysis: boolean = false;
    private zoomRange: {minZoom: number, maxZoom: number};
    
    /**
     * 压平分析
     * @param data 压平分析需要用到的参数
     * @param data.pointArray 压平分析多边形的数组
     * @param data.zoomRange 压平分析在地图的哪些层级有效
     * @param data.zoomRange.minZoom 压平分析生效的最小图层
     * @param data.zoomRange.maxZoom 压平分析生效的最大图层
     * @example let pointArray = [ //3857表示web墨卡托坐标系，4326表示经纬度坐标
            new GE.Point(13376713.90129997, 3540579.2959903134, 0, '3857'),
            new GE.Point(13376711.98134067, 3539826.5375406537, 0, '3857'),
            new GE.Point(13377952.49603797, 3539759.3270052206, 0, '3857'),
            new GE.Point(13378008.185227688, 3540675.3111583497, 0, '3857')
        ];
        let clipData = {
            pointArray: pointArray,  //多边形的顶点数组
            zoomRange: { minZoom: 15, maxZoom: 18 } 
        };
        let clapAnalysis = new GE.ClapAnalysis(clipData);
        let isOpen = clapAnalysis.openAnalysis(); //开启压平分析
        if (!isOpen) {
            alert('请确保压平区域在已加载数据范围内。');
        }
        //clapAnalysis.closeAnalysis(); //关闭压平分析
     */
    public constructor(data: {pointArray: Array<Point>, zoomRange ?: {minZoom: number, maxZoom: number} }) {
        this.app = App.getInstance();
        this.data = data;
        // this.analysis = this.app.analysis;
        this.zoomRange = data.zoomRange;// || {minZoom: 16, maxZoom: 18};
        // this.preLoadTileListener = this.preLoadTileEvent.bind(this);

        let zoomEventListener = Helper.debounce2(this.zoomChangeEvent, 100, false);
        this.app.on(Events.ZoomChange, zoomEventListener, this);
    }

    private preRender(){// 预渲染
        let shapeData = Helper.getCenterPoint(this.data.pointArray);
        let width = shapeData.maxX - shapeData.minX;
        let height = shapeData.maxY - shapeData.minY;
        let shapeCenter = shapeData.centerPoint;
       
        let orthoCameraWidth =  Math.max(width, height);
        let cameraHeight = shapeCenter.z + 10000;
        var orthoCamera = new OrthographicCamera(-orthoCameraWidth, orthoCameraWidth, orthoCameraWidth, -orthoCameraWidth, 0.1, cameraHeight * 2);
        orthoCamera.position.set(shapeCenter.x, shapeCenter.y, cameraHeight);
        orthoCamera.lookAt(shapeCenter.x, shapeCenter.y, shapeCenter.z);
        
        this.renderer = this.app.three.renderer;
        let scene = this.app.three.scene;
        this.preRenderTarget = new WebGLRenderTarget(window.innerWidth*4, window.innerHeight*4, {colorSpace: SRGBColorSpace});
        this.app.three.scene.fog = null; //第一遍渲染，不需要用雾
        this.renderer.setRenderTarget(this.preRenderTarget);
        this.renderer.render(scene, orthoCamera);
        this.app.three.scene.fog = this.app.background.fog; //第一遍渲染完成后，把雾添加回场景
        
        // 保存第一遍渲染的view-project矩阵
        this.previousVP = new Matrix4();
        this.previousVP.multiplyMatrices(orthoCamera.projectionMatrix, orthoCamera.matrixWorldInverse);
        
        this.renderer.setRenderTarget(null);
    }

    private configRender(){
        this.positionArray = [];
        for (let i = 0; i < this.data.pointArray.length; i++) {
            let currentElement = this.data.pointArray[i];
            this.positionArray.push(currentElement.x, currentElement.y);
        }

        if (!this.data.pointArray[0].equals(this.data.pointArray[this.data.pointArray.length - 1])) {
            this.positionArray.push(this.data.pointArray[0].x, this.data.pointArray[0].y); //形成首尾相连的闭环
        }

        this.afterLoadTileListener = this.afterLoadTileEvent.bind(this);
        this.clapData = {isOpenClap: true, clapPointArray: this.positionArray, clapPointCount: this.positionArray.length, renderSide: DoubleSide};
        this.updateLayerCut(this.clapData);
        this.createPolygon();
    }

    private updateLayerCut(clapData){
        Helper.traverseObject(this.app.three.scene, function (obj) {
            if(obj instanceof BaseTileMap && obj.tileType == Const.tileType.imageType){ //影像图层不处理
                return false;
            }

            if (obj.type == 'Mesh') {
                try {
                    if(obj.material.length > 1){
                        obj.material.forEach(element => {
                            if(element.material.setCut){
                                element.material.setCut(clapData);
                            }
                        });
                    }else{
                        if(obj.material.setCut){
                            obj.material.setCut(clapData);
                        }
                    }   
                } catch (error) {
                    console.log(error)
                }
            }
            return true;
        });

        let layerMap = this.app.map.getAllLayer();
        for (let type in layerMap) {
            if(type != Const.tileType.imageType){ //除了影像底图，其他的图层都参与压平分析。之所以影像底图不参与，是为了避免在压平的边缘出现白线，说到底，这是浮点精度造成的问题

                let mapLayers = this.app.map.getLayersByType(type);
                let self = this;
                mapLayers.forEach((layer: BaseTileMap) => {
                    if(clapData.isOpenClap){
                        layer.addEventListener('afterLoadTile' as keyof Object3DEventMap, self.afterLoadTileListener);
                    }else{
                        layer.removeEventListener('afterLoadTile' as keyof Object3DEventMap, self.afterLoadTileListener);
                    }
                });
            }
        }
    }

    private afterLoadTileEvent(eventData){
        let newTile = eventData.message;
        newTile.setCut(this.clapData);
    }

    // private preLoadTileEvent(eventData){
    //     let config = eventData.message;
    //     config['customData'] = this.analysis.customData;
    // }

    private zoomChangeEvent(){
        let zoom = this.app.map.zoom;
        if(!this.polygonMesh || !this.zoomRange){
            return;
        }
        
        if(zoom < this.zoomRange.minZoom || zoom > this.zoomRange.maxZoom){
            this.polygonMesh.visible = false;
        }else{
            this.polygonMesh.visible = true;
        }
    }

    private createPolygon(){
        if(this.polygonMesh){
            this.app.remove(this.polygonMesh);
        }
        let geometry = new CustomPlaneGeometry(this.data.pointArray, true);
        let earClipData = (geometry as CustomPlaneGeometry).earClipData;

        let self = this;
        MaterialShaderManager.addFragmentCode(ShaderConst.fragmentClapProject);
        let macrosList:Array<string> = new Array<string>();
        macrosList.push(ShaderConst.macrosConfig.clapMacros);
        
        let materialParam = {
            coreParameters: {
                map: this.preRenderTarget.texture,
                side: earClipData.isCCW ? FrontSide : BackSide,
                transparent: false,
                depthTest: false,
            },
            macrosList: macrosList,
            identity: 'clapPolygonMaterial'
        };
        let material = new CustomMeshBasicMaterial(materialParam);
        material['setClapProjectData']({isOpenClapProject: true, clapProjectMatrix: self.previousVP});

        this.polygonMesh = new Mesh(geometry, material);
        this.polygonMesh.position.set(earClipData.firstPoint.x, earClipData.firstPoint.y,  earClipData.firstPoint.z);
        this.app.add(this.polygonMesh);
        material.needsUpdate = true;
    }

    /**
     * 开启压平分析
     * @example let pointArray = [ //3857表示web墨卡托坐标系，4326表示经纬度坐标
            new GE.Point(13376713.90129997, 3540579.2959903134, 0, '3857'),
            new GE.Point(13376711.98134067, 3539826.5375406537, 0, '3857'),
            new GE.Point(13377952.49603797, 3539759.3270052206, 0, '3857'),
            new GE.Point(13378008.185227688, 3540675.3111583497, 0, '3857')
        ];
        let clipData = {
            pointArray: pointArray,  //多边形的顶点数组
            zoomRange: { minZoom: 15, maxZoom: 18 } 
        };
        let isOpen = clapAnalysis.openAnalysis(); //开启压平分析
        if (!isOpen) {
            alert('请确保压平区域在已加载数据范围内。');
        }
     */
    public openAnalysis(){
        if(this.isOpenAnalysis){
            return true;
        }
        
        let isOutside = false;
        let pointRange = this.app.map.getMapBound();
        for(let i=0; i<this.data.pointArray.length; i++){
            let element = this.data.pointArray[i].toEPSGWeb();
            if (element.x < pointRange.minPointX || element.x > pointRange.maxPointX || element.y < pointRange.minPointY || element.y > pointRange.maxPointY) {
                isOutside = true;
                break;
            }
        }
        if(isOutside){
            return false;
        }
        
        this.isOpenAnalysis = true;
        this.preRender();
        this.configRender();
        return true;
    }

    /**
     * 关闭压平分析
     * @example clapAnalysis.closeAnalysis();
     */
    public closeAnalysis(){
        this.clapData = {isOpenClap: false, clapPointArray: [], clapPointCount: 0, renderSide: FrontSide};
        this.updateLayerCut(this.clapData);
        
        this.app.remove(this.polygonMesh);
        this.polygonMesh = null;
        this.isOpenAnalysis = false;
    }

}